package de.hs_mannheim.srn.prototyp.model;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.Key;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.List;

import javax.crypto.Cipher;

/**
 * Keybox
 * 
 * @author Tobias Hegemann
 *
 */
public class Keybox {
	/**
	 * List of own encrypted master key packages
	 */
	private List<byte[]> ownEncyptedKeyPackages;
	/**
	 * List of foreign encrypted key packages shared by other users
	 */
	private List<byte[]> foreignEncryptedKeyPackages;
	/**
	 * List for new encrypted key packages shared by other users
	 */
	private List<byte[]> newEncryptedKeyPackages;
	
	
	/* Getter & Setter */
	
	
	
	/* Constructors */
	/**
	 * Initializes a new Keybox for the given owner
	 * @param owner of this new keybox
	 */
	public Keybox() {
		this.ownEncyptedKeyPackages = new ArrayList<byte[]>();
		this.foreignEncryptedKeyPackages = new ArrayList<byte[]>();
	}
	
	
	/* Methods */
	/**
	 * Add an given encrypted KeyPackacge to this Keybox
	 * @param encryptedKeyPackage to add
	 */
	public void add(byte[] encryptedKeyPackage) {
		this.newEncryptedKeyPackages.add(encryptedKeyPackage);
	}
	/**
	 * Import the new keys and sort out duplicates.
	 * Private password of the owner is needed because
	 * here the private key will be used
	 * @param privateKey of the owner
	 */
	public void sort(PrivateKey privateKey) {
		for(byte[] newEncryptedKeyPackage : this.newEncryptedKeyPackages) {
			for(byte[] existingEncryptedKeyPackage : this.foreignEncryptedKeyPackages) {
				if( decryptKeyPackage(privateKey, existingEncryptedKeyPackage).equals( decryptKeyPackage(privateKey, newEncryptedKeyPackage) ) ) {
					this.foreignEncryptedKeyPackages.remove(existingEncryptedKeyPackage);
					this.foreignEncryptedKeyPackages.add(newEncryptedKeyPackage);
					this.newEncryptedKeyPackages.remove(newEncryptedKeyPackage);
				}
			}
		}
	}
	/**
	 * Decrpyt a encrypted KeyPackage and return it.
	 * Null will be returned in case of an wrong password!
	 * @param private key of the user for the decryption
	 * @param encryptedKeyPackage to decrypt
	 * @return the decrypted KeyPackage or null if password was wrong
	 */
	private KeyPackage decryptKeyPackage(PrivateKey privateKey, byte[] encryptedKeyPackage) {
		if(privateKey == null) {
			return null;
		}
		Cipher cipher;
		byte[] decryptedData;
		try {
			cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.DECRYPT_MODE, privateKey);
			decryptedData = cipher.doFinal(encryptedKeyPackage);
		} catch (Exception e) {
			throw new RuntimeException("Spurious decryption error", e);
		}
		return (KeyPackage) deserialize(decryptedData);
	}
	/**
	 * Encrypts the keyPackage with the given Key.
	 * The key can be an public or the private key.
	 * Usually we do only need our or a foreign public key!
	 * @param keyPackage to encrypt
	 * @param key to use for encryption
	 * @return encrypted keyPackage of type byte array
	 */
	private byte[] encryptKeyPackage(KeyPackage keyPackage, Key key) {
		Cipher cipher;
		byte[] encryptedKeyPackage, data = serialize(keyPackage);
		try {
			cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.DECRYPT_MODE, key);
			encryptedKeyPackage = cipher.doFinal(data);
		} catch (Exception e) {
			throw new RuntimeException("Spurious decryption error", e);
		}
		return encryptedKeyPackage;
	}
	/**
	 * Serializes the given Object to byte-code
	 * @param obj Object to serialize
	 * @return serialized object as an byte-array
	 */
	private byte[] serialize(Object obj) {
	    ByteArrayOutputStream out = new ByteArrayOutputStream();
	    ObjectOutputStream os;
		try {
			os = new ObjectOutputStream(out);
			os.writeObject(obj);
			os.close();
		} catch (IOException e) {
			throw new RuntimeException("Spurious serialisation error", e);
		}
	    return out.toByteArray();
	}
	/**
	 * Deserializes the given byte-code into an object
	 * @param data byte-array to deserialize
	 * @return deserialized byte-code as object
	 */
	private Object deserialize(byte[] data) {
	    ByteArrayInputStream in = new ByteArrayInputStream(data);
	    ObjectInputStream is;
	    Object object;
		try {
			is = new ObjectInputStream(in);
			object = is.readObject();
			is.close();
		} catch (IOException e) {
			throw new RuntimeException("Spurious serialisation error", e);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("Spurious object casting error", e);
		}
	    return object;
	}
}
